package com.lhkj.ct.base.utils;

import cn.hutool.core.collection.CollectionUtil;
import com.google.common.collect.ImmutableMap;
import com.lhkj.ct.base.exception.ValidatedException;
import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Stack;
import java.util.regex.Pattern;

/**
 * <p>
 *     数学公式校验工具类
 * </p>
 */
public class FormulaValidUtil {

    private static final ImmutableMap<String, String> map = ImmutableMap.of("{", "}", "[", "]", "(", ")");

    /**
     * Aviator 特定函数和自定义函数
     */
    private static final List<String> fixedVariables = new ArrayList<>(Arrays.asList("math.round", "math.floor", "math.ceil", "math.abs",
            "max", "min", "IF", "between", "differ", "nil", "hld", "spm"));

    /**
     * 使用正则来校验数学公式
     *
     * @param expression 数学公式，包含变量
     * @param variables  内置变量集合
     * @param regex      解析变量的正则表达式
     */
    public static void validate(String expression, List<String> variables, String regex) {
        variables.addAll(fixedVariables);
        // 去空格
        String excludeSpec = expression.replaceAll("[ \\r\\n\\t]", "");
        // 连续运算符处理
        if (excludeSpec.split("[+\\-*/]{2,}").length > 1) {
            throw new ValidatedException("公式不合法，包含连续运算符");
        }
        if (StringUtils.contains(excludeSpec, "()") || StringUtils.contains(excludeSpec, "[]")) {
            throw new ValidatedException("公式不合法，包含空括号");
        }
        excludeSpec = excludeSpec.replaceAll("\\)\\(", "\\)*\\(");
//        expression = expression.replaceAll("\\(\\-", "\\(0-");
//        expression = expression.replaceAll("\\(\\+", "\\(0+");
        // tips 校验变量(因为AviatorUtil不支持中括号和大括号，所以这里不进行分割)
//        String[] splits = expression.split("\\+|\\-|\\*|\\/|\\(|\\)|\\==|\\&&|\\,|\\<=|\\>=|\\<|\\>|IF|NL|XB|\\[|]");
        String[] splits = excludeSpec.split("\\+|-|\\*|/|\\(|\\)|==|&&|,|<=|>=|<|>");
//        Arrays.stream(splits).forEach(System.out::println);
        for (String split : splits) {
            if (StringUtils.isBlank(split) || Pattern.matches("-?(0|([1-9]\\d*))(\\.\\d+)?", split)) {
                continue;
            }
            String realVariable = TemplateUtil.replaceTemplate(split, regex);
            if (CollectionUtil.isNotEmpty(variables) && !variables.contains(realVariable)) {
                throw new ValidatedException("公式不合法，包含非法变量或字符[" + realVariable + "]");
            }
        }
        // 校验括号
        Character preChar = null;
        Stack<Character> stack = new Stack<>();
        for (int i = 0; i < excludeSpec.length(); i++) {
            char currChar = excludeSpec.charAt(i);
            if (i == 0) {
                if (Pattern.matches("[*/]", String.valueOf(currChar))) {
                    throw new ValidatedException("公式不合法，以错误运算符开头");
                }
                if (currChar == '+' || currChar == '-') {
                    excludeSpec = excludeSpec.substring(1);
                }
            }
            if (currChar == '{' || currChar == '[' || currChar == '(') {
                stack.push(currChar);
            }
            //如果是结束符号，则判断当前栈顶元素和该结束符号是否是对应关系
            if (currChar == '}' || currChar == ']' || currChar == ')') {
                if (stack.size() > 0) {
                    String c = String.valueOf(stack.pop());
                    if (currChar != map.get(c).toCharArray()[0]) {
                        throw new ValidatedException("公式不合法，括号不配对");
                    }
                } else {
                    throw new ValidatedException("公式不合法，括号不配对");
                }
            }
            if (preChar != null && preChar == '(' && Pattern.matches("[+\\-*/]+", String.valueOf(currChar))) {
                throw new ValidatedException("公式不合法，左括号后是运算符");
            }
            if (preChar != null && preChar == ')' && !Pattern.matches("[+\\-*/&><=),]+", String.valueOf(currChar))) {
                throw new ValidatedException("公式不合法，右括号后面[" + currChar + "]不是运算符");
            }
            if (i == excludeSpec.length() - 1) {
                if (Pattern.matches("[+\\-*/]", String.valueOf(currChar)))
                    throw new ValidatedException("公式不合法，以运算符结尾");
            }
            preChar = currChar;
        }
        if (stack.size() > 0) {
            throw new ValidatedException("公式不合法，括号不配对");
        }
    }
}
